/*
-----------------------------------------------------------------------------
This file is part of escog.

Copyright (c) 2009 Jacob Essex

escog is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

escog is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with escog.  If not, see <http://www.gnu.org/licenses/>.
-----------------------------------------------------------------------------
*/

#ifdef WX_PRECOMP
#include "wx_pch.h"
#endif

#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__

#include "main.h"

//helper functions
enum wxbuildinfoformat {short_f, long_f};
//--------------------------------------------------------------------
wxString wxbuildinfo(wxbuildinfoformat format) {
    wxString wxbuild(wxVERSION_STRING);
    if (format == long_f ) {
#if defined(__WXMSW__)
        wxbuild << _T("-Windows");
#elif defined(__WXMAC__)
        wxbuild << _T("-Mac");
#elif defined(__UNIX__)
        wxbuild << _T("-Linux");
#endif

#if wxUSE_UNICODE
        wxbuild << _T("-Unicode build");
#else
        wxbuild << _T("-ANSI build");
#endif // wxUSE_UNICODE
    }
    return wxbuild;
}

#ifdef WIN32
std::string getRegKey(const std::string& pos, const std::string& name){
	HKEY hKey;
	TCHAR szProductType[1024];
	DWORD dwBufLen=1024*sizeof(TCHAR);
	LONG lRet;
	lRet = RegOpenKeyEx( HKEY_LOCAL_MACHINE, pos.c_str(), 0, KEY_QUERY_VALUE, &hKey );
	if( lRet != ERROR_SUCCESS )	return "";
	lRet = RegQueryValueEx( hKey, name.c_str(),	NULL, NULL, (LPBYTE) szProductType, &dwBufLen);
	RegCloseKey( hKey );

	if (( lRet != ERROR_SUCCESS )	||	(dwBufLen > 1024*sizeof(TCHAR)) )
		return "";
	return std::string(szProductType);
}
#endif

//--------------------------------------------------------------------
wxString toWX(const std::string& i) {
    return wxString(i.c_str(), wxConvUTF8);
}
//--------------------------------------------------------------------
MainFrame::MainFrame(wxFrame *frame) : GUIMain(frame) {
    //setup coloums
    mActiveList->InsertColumn(0, wxT("File Name"));
    mActiveList->InsertColumn(0, wxT("Value"));
    mActiveList->InsertColumn(0, wxT("GMST"));

    mModList->InsertColumn(0, wxT("Mod"));


    //general loading
    try{
        mEvilList = loadMod("evil.gmst");
    }catch(...){}

#ifdef WIN32
	if ( !mSettings.has("path") )
		mSettings.set("path",getRegKey("Software\\Bethesda Softworks\\Morrowind", "Installed Path"));
#endif

    //get setting, else gets ""
    mFileLocation->SetPath(toWX(mSettings.get("path")));
    iniChanged();
}
//--------------------------------------------------------------------
MainFrame::~MainFrame() {
    //set setting for next time
    mSettings.set("path", std::string(mFileLocation->GetPath().mb_str()));
}
//--------------------------------------------------------------------
MainFrame::EvilAmount MainFrame::isEvil(ESM::RecordPtr r) {
    using namespace ESM;

    if ( !mEvilList ) return EA_NOT;

    RecordPtr er = mEvilList->getRecord(r->getSubRecord(NAME)->getString());
    if ( !er ) //can't find. not evil
        return EA_NOT;

    //doesn't work well yet
    /*
    const FileListCItr end = mBase.end();
    for ( FileListItr itr = mBase.begin(); //loop through base files
            itr != end;
            ++itr ){
        RecordPtr ber = (*itr)->getRecord(r->getSubRecord(NAME)->getString());
        if ( ber && r->equals(ber) ) //found, and is equal to base
            return EA_ASBASE;
    }
    */

    if (r->equals(er) ) //r = evil record. evil
        return EA_EVIL;
    //default
    return EA_NOT;
}
//--------------------------------------------------------------------
void MainFrame::iniChanged() {
    std::string s = std::string(mFileLocation->GetPath().mb_str());
    mModList->DeleteAllItems();
    mActiveList->DeleteAllItems();
    if ( s.length() == 0 ) return;
    //newer version might use this
    //fs::path path = fs::path(s).remove_filename() / "Data Files";
    fs::path path = fs::path(s).remove_leaf() / "Data Files";
    loadMods(path.string());

    //set colum with to the width of the largest item
    mModList->SetColumnWidth(0, wxLIST_AUTOSIZE);
}
//--------------------------------------------------------------------
void MainFrame::loadMods(const std::string& dir) {
    if ( !fs::exists( dir ) )
        return;

    std::map<time_t, std::string> om, omb; //ordered mdos, order mods bases.

    fs::directory_iterator dirEnd;
    for ( fs::directory_iterator itr( dir );  itr != dirEnd;   ++itr ) {

        if ( isBaseFile(itr->leaf()) ) {
            omb[fs::last_write_time(itr->path())] = itr->path().string();
        } else { //check esm/p
            if ( itr->leaf().length() > 3 && (  boost::ends_with(itr->leaf(), "esp") ||
                                                boost::ends_with(itr->leaf(), "esm") ) ) {
                //add to map. ordered by time
                om[fs::last_write_time(itr->path())] = itr->path().string();
            }
        }
    }

    //map should be sorted by time. NOTE: This also calls LOADMOD. See FUNCTION CALL
    for ( std::map<time_t, std::string>::iterator itr = om.begin(); itr != om.end(); ++itr )
        addModToGUI(fs::path(itr->second).leaf(), (loadModToList(itr->second, mMods)>0));

    //load base
    for ( std::map<time_t, std::string>::iterator itr = omb.begin(); itr != omb.end(); ++itr )
        loadModToList(itr->second, mBase);
}
//--------------------------------------------------------------------

int MainFrame::loadModToList(const std::string& file, std::list<ESM::FilePtr>& a) {
    using namespace ESM;
    try {
        FilePtr f = loadMod(file);
        a.push_back(f);
        return f->getRecordsByType(GMST).size();
    } catch (Exception& e) {
        std::cout << e;
    }
    return 0;
}
//--------------------------------------------------------------------
ESM::FilePtr MainFrame::loadMod(const std::string& file) {
    ESM::FilePtr f(new ESM::File(false));
    f->addLoadable(ESM::GMST);
    f->setKey(ESM::GMST, ESM::NAME);
    f->load(file);
    return f;
}
//--------------------------------------------------------------------
void MainFrame::addModToGUI(const std::string& name, bool hasGMSTs /*= false*/) {
    wxListItem item;

    item.SetMask(wxLIST_MASK_TEXT);
    item.SetId(0);
    item.SetText(toWX(name));

    if ( hasGMSTs )
        item.SetTextColour(wxColour(*wxRED));

    mModList->InsertItem(item);
}
//--------------------------------------------------------------------
void MainFrame::getActiveGMSTs(bool everything/* = true */, std::set<std::string> filter/* = std::set*/) {
    using namespace ESM;

    //map of active gmsts. ID, Record and File
    ActiveGMSTMap active;

    const FileListCItr end = mMods.end();
    for ( FileListItr itr = mMods.begin(); //loop through files
            itr != end;
            ++itr ) {

        if ( !everything && filter.find(getFileName((*itr)->getLoadedFile())) == filter.end() )
            continue;

        RecordList rl = (*itr)->getRecordsByType(GMST);
        const RecordListCItr rend = rl.end();
        for (RecordListItr ritr = rl.begin(); ritr != rend; ++ritr ) { //every gmst in that file
            RecordPtr r = *ritr;
            active[r->getSubRecord(NAME)->getString()] = std::make_pair<ESM::RecordPtr,ESM::FilePtr>(r, *itr); //store it as the current active gmst for this name
        }
    }

    //add to record thingy
    addActiveGMST(active);
}

//--------------------------------------------------------------------
void MainFrame::addActiveGMST(ActiveGMSTMap& active) {
    mActiveList->DeleteAllItems();
    for ( ActiveGMSTMap::iterator itr = active.begin();
            itr != active.end();
            ++itr ) {
        addGMST(itr->second.first, itr->second.second);

    }
}
//--------------------------------------------------------------------
void MainFrame::addGMST(ESM::RecordPtr r, ESM::FilePtr fp) {
    using namespace ESM;

    wxString val;

    //get the corerect sub record
    if ( r->hasSubRecord(STRV) )
        val = toWX(r->getSubRecord(STRV)->getString());
    else if ( r->hasSubRecord(INTV) )
        val = wxString::Format(wxT("%i"), r->getSubRecord(INTV)->getLong());
    else if ( r->hasSubRecord(FLTV) )
        val = wxString::Format(wxT("%f"), r->getSubRecord(FLTV)->getFloat());

    wxListItem item;

    item.SetMask(wxLIST_MASK_TEXT);
    item.SetId(0);
    item.SetText(toWX(r->getSubRecord(ESM::NAME)->getString()));

    EvilAmount ea = isEvil(r); //quite expensive, so cached
    if ( ea == EA_EVIL )
        item.SetTextColour(wxColour(*wxRED));
    else if ( ea == EA_ASBASE )
        item.SetTextColour(wxColour(*wxBLUE));

    long itemIndex = mActiveList->InsertItem(item);
    mActiveList->SetItem(itemIndex,1, val);
    mActiveList->SetItem(itemIndex,2, toWX(fs::path(fp->getLoadedFile()).leaf()));
}
//--------------------------------------------------------------------
void MainFrame::onModSelect( wxListEvent& event ) {
reloadActive();
}
//--------------------------------------------------------------------
void MainFrame::onCleanEvil( wxCommandEvent& event ){
	using namespace ESM;
	if ( !mEvilList ) return;
	StringList sl = getSelectedMods();

	std::map< std::string, std::list<std::string> > om;

	for ( StringList::const_iterator itr = sl.begin(); itr != sl.end(); ++itr ) {
		FilePtr f = getFile(*itr);
		RecordList rl = f->getRecordsByType(GMST);
		for ( RecordListItr ritr = rl.begin(); ritr != rl.end(); ++ritr ){
			if ( isEvil(*ritr) != EA_NOT ){
				om[*itr].push_back((*ritr)->getSubRecord(NAME)->getString());
			}
		}
	}
	removeFromFiles(om);
}
//--------------------------------------------------------------------
void MainFrame::onIniChanged( wxFileDirPickerEvent& event ){
	iniChanged();
}
//--------------------------------------------------------------------
void MainFrame::onShowActive( wxCommandEvent& event ){
	getActiveGMSTs();
}
//--------------------------------------------------------------------
void MainFrame::reloadActive(){
	mActiveList->DeleteAllItems();

	StringList sl = getSelectedMods();

	//convert to std::set
	std::set<std::string> ss;
	for ( StringList::iterator itr = sl.begin(); itr != sl.end(); ++itr ) {
		ss.insert(*itr);
	}
	getActiveGMSTs(false,ss);
}
//--------------------------------------------------------------------
ESM::FilePtr MainFrame::getFile( const std::string& str ){
	const FileListCItr end = mMods.end();
	for ( FileListItr itr = mMods.begin(); //loop through files. get one with the correct name
		itr != end;
		++itr ) {
			if ( getFileName((*itr)->getLoadedFile()) == str ) {
				return (*itr);
			}
	}
	return ESM::FilePtr();
}
//--------------------------------------------------------------------
void MainFrame::onRemoveSelected( wxCommandEvent& event ){
	GMSTFileList selected = getSelectedGMSTs();

	std::map< std::string, std::list<std::string> > om;

	//group by file
	for ( GMSTFileList::iterator itr = selected.begin();
		itr != selected.end();
		++itr ) {
			std::string s1, s2;
			s1 = itr->first;
			s2 = itr->second;
			om[itr->second].push_back(itr->first);
	}
	removeFromFiles(om);
}
//--------------------------------------------------------------------
void MainFrame::removeFromFiles( std::map< std::string, std::list<std::string> >& om ){
	//for each file
	for ( std::map< std::string, std::list<std::string> >::iterator itr = om.begin();
		itr != om.end();
		++itr) {
			try {
				ESM::FilePtr f = getFile(itr->first);

				//remove from file on harddrive
				removeGMSTs(f->getLoadedFile(), itr->second);

				//from from file in mem
				for ( std::list<std::string>::iterator itr2 = itr->second.begin();
					itr2 != itr->second.end();
					++itr2) {
						f->deleteRecord(*itr2);
				}

				//change colour if we have removed all GMSTs
				checkFileColour(itr->first);
			} catch (ESM::Exception& e) {
				std::cout << e;
			}
	}

	reloadActive();
}
//--------------------------------------------------------------------
void MainFrame::removeGMSTs( const std::string& file, std::list<std::string>& names ){
	using namespace ESM;
	File f(true);
	f.setKey(GMST, NAME);
	f.load(file);

	//for every item, delete the record
	for ( std::list<std::string>::iterator itr = names.begin();
		itr != names.end();
		++itr) {
			f.deleteRecord(*itr);
	}

	//save...
	f.save(file);
}
//--------------------------------------------------------------------
void MainFrame::setSelectedMods( StringList& sl ){
	for ( StringList::const_iterator itr = sl.begin(); itr != sl.end(); ++itr ) {
		long i = mModList->FindItem  	(-1, toWX(*itr));
		mModList->SetItemState(i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
	}
}
//--------------------------------------------------------------------
std::list<std::string> MainFrame::getSelectedMods(){
	std::list<std::string> r;
	long itemIndex = -1;
	for (;;) {
		itemIndex = mModList->GetNextItem(itemIndex,
			wxLIST_NEXT_ALL,
			wxLIST_STATE_SELECTED);
		if (itemIndex == -1) break;
		r.push_back(std::string(mModList->GetItemText(itemIndex).mb_str()));
	}
	return r;
}
//--------------------------------------------------------------------
MainFrame::GMSTFileList MainFrame::getSelectedGMSTs(){
	GMSTFileList r;
	long itemIndex = -1;
	for (;;) {
		itemIndex = mActiveList->GetNextItem(itemIndex,
			wxLIST_NEXT_ALL,
			wxLIST_STATE_SELECTED);
		if (itemIndex == -1) break;
		r.push_back(
			std::make_pair<std::string, std::string>(
			std::string(getCellValue(mActiveList,itemIndex, 0).mb_str()), //gmst name
			std::string(getCellValue(mActiveList,itemIndex, 2).mb_str())  //file name
			));
	}
	return r;
}
//--------------------------------------------------------------------
void MainFrame::checkFileColour( const std::string& file ){
	using namespace ESM;
	FilePtr f = getFile(file);
	long i = mModList->FindItem(-1, toWX(file));
	if ( f->getRecordsByType(GMST).size() == 0 ){
		setItemColour(i, wxColour(*wxBLACK));
	}else{
		setItemColour(i, wxColour(*wxRED));
	}
}
//--------------------------------------------------------------------
void MainFrame::setItemColour( long row, wxColour c ){
	wxListItem item;

	item.SetId(row);
	mModList->GetItem(item);

	item.SetTextColour(c);
	mModList->SetItem(item);
}
//--------------------------------------------------------------------
